// 8.1 IO类
/**
 * 三个头文件：iostream定义了用于读写流的基本类型，fstream定义了读写命名文件的类型，sstream定义了读写内存string对象的类型。
 * 宽字符版本的类型和对象与其对应的普通char版本的类型定义在同一个头文件中。例如，头文件fstream定义了ifstream和wifstream类型。
 * 类型ifstream和istringstream都继承自istream。因此，我们可以像使用istream对象一样来使用ifstream和istringstream对象。
 */

#include <iostream>
#include <fstream>
using std::cerr, std::clog;
using std::cin, std::cout, std::endl, std::flush, std::ends, std::unitbuf, std::nounitbuf;
using std::ostream, std::istream;
using std::ofstream;
using std::string;

int main()
{
    // 8.1.1 IO对象无拷贝或赋值
    /*
      由于不能拷贝IO对象，因此我们也不能将形参或返回类型设置为流类型（参见6.2.1节，第188页）。
      进行IO操作的函数通常以引用方式传递和返回流。读写一个IO对象会改变其状态，因此传递和返回的引用不能是const的。
    */
    ofstream out1, out2;
    out1 = out2;              // 错误，不能对流对象赋值
    ofstream print(ofstream); // 错误，不能初始化ofstream参数
    out2 = print(out2);       // 错误，不能拷贝流对象

    // 8.1.2 条件状态
    /*
      IO操作一个与生俱来的问题是可能发生错误。一些错误是可恢复的，而其他错误则发生在系统深处，已经超出了应用程序可以修正的范围。
      IO类所定义的一些函数和标志，可以帮助我们访问和操纵流的条件状态（condition state）。strm是一种IO类型，如：iostream、fstream、sstream。
      strm::iostate  是一种机器相关的类型，提供了表达条件状态的完整功能
      strm::badbit   用来指出流已崩溃
      strm::failbit  用来指出一个IO操作失败了
      strm::eofbit   用来指出流达到了文件结束
      strm::goodbit  用来指出流未处于错误状态，此值保证是0
      s.eof()            若流s的eofbit置位，则返回true
      s.fail()           若流s的failbit或badbit置位，则返回true
      s.bad()            若流s的badbit置位，则返回true
      s.good()           若流s处于有效状态，则返回true
      s.clear()          将流s中所有条件状态位复位，将流的状态设置为有效。返回void
      s.clear(flags)     根据给定的flags标志位，将流s中对应条件状态位复位。flags的类型为strm::iostate。返回void
      s.setstate(flags)  根据给定的flags标志位，将流s中对应条件状态位置位。flags的类型为strm::iostate。返回void
      s.rdstate()        返回流s的当前条件状态，返回值类型为strm::iostate
      由于流可能处于错误状态，因此代码通常应该在使用一个流之前检查它是否处于良好状态。
      确定一个流对象的状态的最简单的方法是将它当作一个条件来使用.
    */
    string word;
    while (cin >> word) // while循环检查>>表达式返回的流的状态。如果输入操作成功，流保持有效状态，则条件为真。
    {
        // ok，读操作成功
    }
    auto old_state = cin.rdstate(); // 记住cin的当前状态
    cin.clear();                    // 使cin有效
    process_input(cin);             // 使用cin，这里process_input是伪代码，代表处理cin
    cin.setstate(old_state);        // 将cin置位原有状态
    // 为了复位单一的条件状态位，我们首先用rdstate读出当前条件状态，然后用位操作将所需位复位来生成新的状态。
    // 例如，下面的代码将failbit和badbit复位，但保持其他标志位不变：
    cin.clear(cin.rdstate() & ~cin.failbit & ~cin.badbit);

    // 8.1.3 管理输出缓冲
    /*
      每个输出流都管理一个缓冲区，用来保存程序读写的数据。
      由于设备的写操作可能很耗时，允许操作系统将多个输出操作组合为单一的设备写操作可以带来很大的性能提升。
      导致缓冲刷新（即，数据真正写到输出设备或文件）的原因有很多：
      1. 在每个输出操作之后，我们可以用操纵符unitbuf设置流的内部状态，来清空缓冲区。
         默认情况下，对cerr是设置unitbuf的，因此写到cerr的内容都是立即刷新的
      2. 一个输出流可能被关联到另一个流。在这种情况下，当读写被关联的流时，关联到的流的缓冲区会被刷新。
         例如，默认情况下，cin和cerr都关联到cout。因此，读cin或写cerr都会导致cout的缓冲区被刷新。
      刷新输出缓冲区：除了endl操纵符，还有flush和ends。
      unitbuf操纵符：如果想在每次输出操作后都刷新缓冲区，我们可以使用unitbuf操纵符。它告诉流在接下来的每次写操作之后都进行一次flush操作。
      而nounitbuf操纵符则重置流，使其恢复使用正常的系统管理的缓冲区刷新机制。
      警告：如果程序崩溃，输出缓冲区不会被刷新。
      交互式系统通常应该关联输入流和输出流。这意味着所有输出，包括用户提示信息，都会在读操作之前被打印出来。
      每个流同时最多关联到一个流，但多个流可以同时关联到同一个ostream。
    */
    cout << "hi!" << endl;  // 输出hi!，和一个换行，然后刷新缓冲区
    cout << "hi!" << flush; // 输出hi!，然后刷新缓冲区，不附加任何额外字符
    cout << "hi!" << ends;  // 输出hi!，和一个空字符，然后刷新缓冲区
    cout << unitbuf; // 所有输出操作后都会立即刷新缓冲区
    cout << nounitbuf; // 回到正常的缓冲方式
    cin.tie(&cout); // 仅仅是用来展示：标准库将cin和cout关联在一起
    ostream *old_tie = cin.tie(nullptr); // cin不再与其他流关联
    cin.tie(&cerr); // 将cin与cerr关联，这不是个好主意。读取cin刷新cerr而不是cout
    cin.tie(old_tie); // 重建cin和cout的关联
    // 每个流同时最多关联到一个流，但多个流可以同时关联到同一个ostream。

    return 0;
}